package com.sali.commons.aop.limiter;

import com.google.common.collect.Lists;
import com.sali.commons.annotation.AccessLimiter;
import com.sali.utils.exception.BusinessException;
import com.sali.utils.result.ResultEnum;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.stream.Collectors;



@Slf4j
@Aspect
@Component
public class AccessLimiterAspect {


    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Autowired
    @Qualifier("userlimit")
    private DefaultRedisScript<Boolean> defaultRedisScript;

    // 定义aop的切入点
    @Pointcut("@annotation(com.sali.commons.annotation.AccessLimiter)")
    public void cut() {
        log.info("cut");
    }

    // 拦截属于放入执行之前的行为，所以定义@Before通知
    @Before("cut()")
    public void before(JoinPoint joinPoint) {
        // 获取方法的签名作为key
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        Method method = methodSignature.getMethod();
        // 获取方法上AccessLimiter注解
        AccessLimiter annotation = method.getAnnotation(AccessLimiter.class);
        //如果没有增加accessLimiter注解说明不需要限流，直接返回
        if (annotation == null) {
            return;
        }
        // 获取限流缓存的key
        String key = annotation.key();
        // 获取限流缓存的限制次数
        Integer limit = annotation.limit();
        // 获取限流缓存的时间
        Integer timeout = annotation.timeout();

        // 如果没有设置key，从调用方法的签名自动生成一个key
        if (StringUtils.isEmpty(key)) {
            // 获取方法所有的参数
            Class<?>[] types = method.getParameterTypes();
            // 获取方法的签名
            key = method.getName();
            // 如果又参数的话，用参数和key来确定唯一
            if (types != null) {
                String paramTypes = Arrays.stream(types).map(Class::getName)
                        .collect(Collectors.joining(","));
                log.info("param types : {}", paramTypes);
                key = key + "#" + paramTypes;
            }
        }

        // 请求lua脚本
        Boolean acquired = stringRedisTemplate.execute(defaultRedisScript,
                Lists.newArrayList(key,key), limit.toString(), timeout.toString());
        if (!acquired) {
            throw new BusinessException(ResultEnum.LIMITE_SERVER_ERROR);
        }
    }
}
